package trb.complexshield.benchmarking;

import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;
import no.hrp.hvrc.rad.calc.intersect.raytrace.CollisionMesh;
import no.hrp.hvrc.rad.calc.intersect.raytrace.RayCollisionPacket;
import no.hrp.hvrc.utils.j3d.GeometryExtractor;
import no.hrp.hvrc.utils.j3d.Mat4;
import no.hrp.hvrc.utils.j3d.Vec3;
import no.hrp.hvrc.utils.j3d.geometry.ResizableSphere;
import trb.complexshield.ComplexShield;
import trb.complexshield.bool.model.BoolSphere;
import trb.complexshield.closedmesh.BvhCollisionMesh;
import trb.complexshield.closedmesh.ClosedMesh;
import trb.complexshield.voxel.VoxelFromBool;

public class BenchmarkSphere {

    public final float radius;
    public final Mat4 mat4 = new Mat4();

    public BenchmarkSphere(float radius, Mat4 mat4) {
        this.radius = radius;
        this.mat4.set(mat4);
    }

    public static ComplexShield createBool(BenchmarkSphere sphere) {
        return BenchmarkTub.createComplexShieldFromBoolModel(createBoolModel(sphere));
    }


    public static ComplexShield createVoxel(BenchmarkSphere sphere, int dim) {
        return VoxelFromBool.createVoxel(createBoolModel(sphere), dim);
    }

    public static Object createBoolModel(BenchmarkSphere sphere) {
        return new BoolSphere(sphere.radius, sphere.mat4);
    }

    public static ClosedMesh createClosedMesh(BenchmarkSphere sphere, int divisions, AtomicInteger triangleCount) {
        GeometryExtractor geometryExtractor = createExtractor(sphere, divisions);
        triangleCount.set(geometryExtractor.getIndices().length / 3);
        return new ClosedMesh(geometryExtractor.getCoords(), geometryExtractor.getIndices(), sphere.mat4);
    }

    public static BvhCollisionMesh createBvhMesh(BenchmarkSphere sphere, int divisions, AtomicInteger triangleCount) {
        GeometryExtractor geometryExtractor = createExtractor(sphere, divisions);
        triangleCount.set(geometryExtractor.getIndices().length / 3);
        return new BvhCollisionMesh(geometryExtractor.getCoords(), geometryExtractor.getIndices(), sphere.mat4);
    }

    /**
     * // scaleFactor1
     mesh 1 divisions, 8 tris,  error 33% dif:11777.563 correctTotal:35594.52 testTotal:46941.406 31.878183
     mesh 2 divisions, 32 tris,  error 7% dif:2399.0999 correctTotal:35594.52 testTotal:35534.156 0.16958588
     mesh 3 divisions, 72 tris,  error 3% dif:1035.3303 correctTotal:35594.52 testTotal:35468.074 0.35523814
     mesh 4 divisions, 128 tris,  error 2% dif:583.99646 correctTotal:35594.52 testTotal:35502.453 0.2586533
     mesh 5 divisions, 200 tris,  error 1% dif:369.46585 correctTotal:35594.52 testTotal:35531.324 0.17754225
     mesh 10 divisions, 800 tris,  error 0% dif:93.94344 correctTotal:35594.52 testTotal:35573.234 0.05979897

     // scaleFactor2
     mesh 1 divisions, 8 tris,  error 26% dif:9414.043 correctTotal:35594.52 testTotal:43864.914 23.235022
     mesh 2 divisions, 32 tris,  error 7% dif:2410.0173 correctTotal:35594.52 testTotal:35846.46 0.70780957
     mesh 3 divisions, 72 tris,  error 3% dif:1028.9265 correctTotal:35594.52 testTotal:35647.066 0.14762631
     mesh 4 divisions, 128 tris,  error 2% dif:576.93756 correctTotal:35594.52 testTotal:35611.56 0.047869906
     mesh 5 divisions, 200 tris,  error 1% dif:364.92322 correctTotal:35594.52 testTotal:35603.24 0.024494642
     mesh 10 divisions, 800 tris,  error 0% dif:92.36127 correctTotal:35594.52 testTotal:35592.15 -0.006661401     */
    public static GeometryExtractor createExtractor(BenchmarkSphere sphere, int divisions) {
        int trianglesAroundEquator = divisions * 4;
        double angle = Math.PI * 2 / trianglesAroundEquator;
        Vec3 edgePoint1 = new Vec3().interpolate_(new Vec3(1, 0, 0), new Vec3(Math.cos(angle), Math.sin(angle), 0), 0.5f);
        Vec3 edgePoint2 = new Vec3().interpolate_(new Vec3(1, 0, 0), new Vec3(Math.cos(angle), 0, Math.sin(angle)), 0.5f);
        Vec3 midPoint = new Vec3().interpolate_(edgePoint1, edgePoint2, 0.5f);

        Vec3 corner1 = new Vec3(1, 0, 0);
        Vec3 corner2 = new Vec3(Math.cos(angle), 0, Math.sin(angle));
        Vec3 corner3 = new Vec3(Math.cos(angle), Math.sin(angle), 0);
        Vec3 midPoint2 = new Vec3().add_(corner1).add_(corner2).add_(corner3).scale(1/3.0);

        float scaleFactor = 1f / midPoint.length();
        float scaleFactor2 = 1f / (1 - (1 - midPoint2.length()) * 0.88f);
        //System.out.println("divisions " + divisions + " " + angle + " " + midPoint + " " + scaleFactor + " " + scaleFactor2);

        ResizableSphere resizableSphere = new ResizableSphere(new Vec3(), sphere.radius * scaleFactor2, divisions);
        return GeometryExtractor.extractInWorldSpace(resizableSphere.getRootBG());
    }

    public static float findTessellatedRadiusWithSameVolume(float radius, int divisions) {
        double realVolume = calculateSphereVolume(radius);
        float minRadius = 0f;
        double minVolume = 0.0;
        float maxRadius = radius * 2;
        double maxVolume = calculateVolume(maxRadius, divisions);
        for (int i=0; i<100; i++) {
            //System.out.println("binary search iteration " + i);
            float testRadius = (minRadius + maxRadius) / 2f;
            float testVolume = calculateVolume(testRadius, divisions);
//            if (testVolume <= minVolume) {
//                maxRadius = minRadius;
//                //System.out.println("fail min after " + i);
//                break;
//            }
//            if (testVolume >= maxVolume) {
//                //System.out.println("fail max after " + i);
//                minRadius = maxRadius;
//                break;
//            }

            if (testVolume < realVolume) {
                minRadius = testRadius;
                minVolume = testVolume;
            } else {
                maxRadius = testRadius;
                maxVolume = testVolume;
            }
        }

        return (minRadius + maxRadius) / 2f;
    }

    private static double calculateSphereVolume(float radius) {
        return 4.0 / 3.0 * Math.PI * radius * radius * radius;
    }

    public static float calculateVolume(float radius, int divisions) {
        ResizableSphere resizableSphere = new ResizableSphere(new Vec3(), radius, divisions);
        GeometryExtractor geometryExtractor = GeometryExtractor.extractInWorldSpace(resizableSphere.getRootBG());
        CollisionMesh collisionMesh = new CollisionMesh(geometryExtractor.getCoords(), geometryExtractor.getIndices());
        int cnt = 100000;
        int insideCnt = 0;
        RayCollisionPacket rayCollisionPacket = new RayCollisionPacket();
        Random random = new Random(/*1234567*/);
        for (int i=0; i<cnt; i++) {
            Point3f pos = new Point3f(random.nextFloat(), random.nextFloat(), random.nextFloat());
            pos.scaleAdd(radius*2, new Vec3(-radius, -radius, -radius));
            rayCollisionPacket.reset(pos, new Vector3f(1, 0, 0));
            collisionMesh.bruteRayCollisionDetection(rayCollisionPacket);
            if ((rayCollisionPacket.results.size() % 2) == 1) {
                insideCnt++;
            }
        }
        float factor = insideCnt / (float) (cnt - 1);
        //System.out.println("factor " + factor + " " + (calculateSphereVolume(radius) / ((radius*2) * (radius * 2) * (radius * 2))));
        return ((radius*2) * (radius * 2) * (radius * 2))*factor;
    }

    /**
     division=1  r=1.4526327
     division=2  r=1.1198497
     division=3  r=1.0520658
     division=4  r=1.0277307
     division=5  r=1.017633
     division=6  r=1.0112946
     division=7  r=1.0077896
     division=8  r=1.0055664
     division=9  r=1.0042031
     division=10 r=1.0031693
     division=11 r=1.0028472
     division=12 r=1.0018189

     division=1 r=1.4644718
     division=2 r=1.124629
     division=3 r=1.054199
     division=4 r=1.0295405
     division=5 r=1.0203209
     division=6 r=1.0138838
     division=7 r=1.0107903
     division=8 r=1.0065908
     division=9 r=1.0053711
     division=10 r=1.0053713
     division=11 r=1.0053787
     division=12 r=1.0030429
     division=13 r=1.0034192
     division=14 r=1.0020447
     division=15 r=1.001585
     division=16 r=1.001581
     division=17 r=1.002079
     division=18 r=1.000366
     *
     * @param args
     */
    public static void main(String[] args) {
        for (int divisions=1; divisions<20; divisions++) {
            float r = findTessellatedRadiusWithSameVolume(1f, divisions);
            System.out.println("division=" + divisions + " r=" + r);
        }
    }
}
